#ifndef _UTILS_TYPE_TRAITS_H_
#define _UTILS_TYPE_TRAITS_H_

// is_same
namespace utils
{

template <typename _T1, typename _T2>
struct is_same
{ static constexpr bool value = false; };

template <typename _T>
struct is_same<_T, _T>
{ static constexpr bool value = true; };

} // utils

// enable_if
namespace utils
{

template <bool _Val, typename _Type = void>
struct enable_if {};

template <typename _Type>
struct enable_if<true, _Type>
{ typedef _Type type; };

} // utils

// remove_qualifier
namespace utils
{

template <typename _Type>
struct remove_qualifier
{ typedef _Type type; };

template <typename _Type>
struct remove_qualifier<const _Type>
{ typedef remove_qualifier<_Type> type; };

template <typename _Type>
struct remove_qualifier<volatile _Type>
{ typedef remove_qualifier<_Type> type; };

} // utils

// remove_reference
namespace utils
{

template <typename _Type>
struct remove_reference
{ typedef _Type type; };

template <typename _Type>
struct remove_reference<_Type&>
{ typedef _Type type; };

} // utils

// is_integral
namespace utils
{

namespace detail
{

template <typename _Type>
struct is_integral
{ static constexpr bool value = false; };

#define _DECLARE_AS_INTEGRAL(_type) \
  template <> struct is_integral<_type> \
  { static constexpr bool value = true; }

_DECLARE_AS_INTEGRAL(signed char);
_DECLARE_AS_INTEGRAL(unsigned char);
_DECLARE_AS_INTEGRAL(signed short int);
_DECLARE_AS_INTEGRAL(unsigned short int);
_DECLARE_AS_INTEGRAL(signed int);
_DECLARE_AS_INTEGRAL(unsigned int);
_DECLARE_AS_INTEGRAL(signed long int);
_DECLARE_AS_INTEGRAL(unsigned long int);
_DECLARE_AS_INTEGRAL(signed long long int);
_DECLARE_AS_INTEGRAL(unsigned long long int);

#undef _DECLARE_AS_INTEGRAL

} // detail

template <typename _Type>
struct is_integral
{
  static constexpr bool value =
      detail::is_integral<
      typename remove_reference<
      typename remove_qualifier<_Type>::type>::type>::value;
};

} // utils

// is_floating_point
namespace utils
{

namespace detail
{

template <typename _T>
struct is_floating_point
{ static constexpr bool value = false; };

#define _DECLARE_AS_FLOATING_POINT(_type) \
  template <> struct is_floating_point<_type> \
  { static constexpr bool value = true; }

_DECLARE_AS_FLOATING_POINT(float);
_DECLARE_AS_FLOATING_POINT(double);
_DECLARE_AS_FLOATING_POINT(long double);

#undef _DECLARE_AS_FLOATING_POINT

} // detail

template <typename _Type>
struct is_floating_point
{
  static constexpr bool value =
      detail::is_floating_point<
      typename remove_reference<
      typename remove_qualifier<_Type>::type>::type>::value;
};

} // utils

// is_basic_type
namespace utils
{

template <typename _Type>
struct is_basic_type
{
  static constexpr bool value =
      is_floating_point<_Type>::value ||
      is_integral<_Type>::value;
};

} // utils

// apply_function_scope
namespace utils
{

template <typename _Function,
          template <typename, typename...> class _Class,
          typename...>
struct apply_function_scope {};

template <template <typename, typename...> class _Class,
          typename _Return, typename... _Args, typename... _Head>
struct apply_function_scope<_Return(_Args...), _Class, _Head...>
{ typedef _Class<_Head..., _Return, _Args...> type; };

} // utils

#endif // _UTILS_TYPE_TRAITS_H_
